Key

scroll

Key Blog

  • Key 主頁>
  • 博客>
  • [ue5] unreal insights를 활용한 최적화: async loading을 통한 게임 성능 향상
  • [UE5] 利用Unreal Insights進行優化:透過非同步加載(AsyncLoad)提升遊戲性能

    @kiikey4(Key Zhao)

    [UE5] 利用Unreal Insights進行優化:透過非同步加載(AsyncLoad)提升遊戲性能

    最後更新日期 2024年10月20日

    發佈日期 2024年10月19日

    1

    概要

    本文介紹使用 Unreal Insight 進行優化負荷調查的流程,以及如何使用 C++ 實現非同步加載(預先讀取)以防止在遊戲內使用 LoadSynchronous() 同步加載素材時發生的停頓。關於 Blueprint 的非同步加載,請參考下面的參考連結。

    環境

    • Rider 2024.2.6
    • Unreal Engine 5.4
    • Windows 11 Pro

    參考資料

    本文

    使用 Unreal Insight 進行優化的負荷調查

    Unreal Insights 是什麼?
    Unreal Insights 是一個用於分析 Unreal Engine 中性能和內存使用情況的工具。官方文檔在這裡: https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-insights-in-unreal-engine

    使用方法
    有關詳細的使用方法,請參考 [UE5] 嘗試使用 Unreal Insights

    進行優化調查時,理想情況下應使用目標平台的Package版本,而非普通的 PIE(在編輯器中播放)模式。因為在 PIE 模式下,預先加載的緩存和背景操作會影響,導致難以進行準確的負荷測量。

    這次我們將在接近Package版本的 Standalone Game 中進行調查。

    如何啟動 Standalone Game

    如下面的圖片所示,從 UE 編輯器啟動 Standalone Game。

    how_to_launch_standalone_game_nbf9t2

    如果在遊玩過程中發生了卡頓,可以使用以下命令。

    stat_unitgraph_qvvmlg

    stat unitgraph

    「stat UnitGraph」命令是用來可視化遊戲處理負荷的工具,以圖表的形式顯示。這樣一來,在發生停頓的時候,圖表上會出現明顯的尖峰,能夠清楚地識別問題所在。

    與許多人常用的「stat Unit」或「stat fps」不同,「stat UnitGraph」能夠記錄短暫的停頓,較少漏掉。

    執行後會顯示如下圖表。左下角顯示負荷圖,右上角顯示具體數據。若以 60fps 為目標,理想的情況下幀時間應保持在16.6ms以下。

    stat_unitgraph_explanation_mdv1fe

    接下來,我們將使用 Unreal Insights 進行實際測量。

    trace_start_aq3qbs

    trace.starttrace.stop

    要開始使用 Unreal Insights 進行測量,請執行 trace.start 命令。測量完成後,使用 trace.stop 結束。

    處理負荷測量結果

    如下面的視頻所示,確認到卡頓的發生。

    hitch_graph_nl0bb9

    當重處理執行時,圖表上會顯示出明顯的變化。

    hitch_frame_gucxrj

    遊戲: 58.26ms
    可以看到遊戲線程耗時 58.26ms。

    接下來,我們也來檢查一下 Unreal Insights 的測量結果。

    如何打開 Trace
    open_trace_ws9cha

    打開 Trace 數據後,會顯示如下結果。 HitchhUnrealInsight_wlbcuz

    在這個結果中,值得關注的是綠色條所示的

    LoadObject (154.7ms) - /Game/Main/InGame/VFX/Niagara/NS_DizzyStar.NS_DizzyStar
    

    這裡顯示了 Niagara 效果的加載過程對系統造成了很大的負擔。

    尤其是在 Niagara 的初次載入或初次生成時,可能會因為着色器編譯而導致卡頓現象。

    這次我會解釋初次載入時出現卡頓的原因,不過如果是初次生成時出現卡頓的情況,可以通過事前(例如在黑屏過場等)在看不見的地方進行一次生成來解決。

    在Package版本中,這種停頓只會在安裝後的第一次發生。
    而在 UE 中,則僅在 UE 啟動後的第一次會發生。

    如果想要重現停頓,則需要重啟 Unreal Engine 或刪除並重新安裝包。

    這是造成問題的 Niagara 效果。

    DizzyNiagaraEffect_x8gioz

    查看 C++ 代碼後,加載過程如下所示。

    PlayerCharacter.h
    1public: 2 //... 3 UPROPERTY(EditAnywhere, BlueprintReadWrite) 4 TSoftObjectPtr<UNiagaraSystem> DizzyEffectAsset; 5 //...
    PlayerCharacter.cpp
    1void APlayerCharacter::StartDizzy() 2{ 3 if (IsDizzy) 4 { 5 return; 6 } 7 CharacterMovementComponent->MaxWalkSpeed = DizzySpeed; 8 9 10 IsDizzy = true; 11 12 UNiagaraSystem* DizzyEffectSystem = DizzyEffectAsset.LoadSynchronous(); 13 if (!IsValid(DizzyEffectSystem)) 14 { 15 UE_LOG(LogTemp, Error, TEXT("DizzyEffectSystem is null, Function name: %s"), *FString(__FUNCTION__)); 16 } 17 DizzyEffect = UNiagaraFunctionLibrary::SpawnSystemAttached(DizzyEffectSystem, SceneComponent, NAME_None, 18 DizzyEffectOffset, FRotator::ZeroRotator, 19 EAttachLocation::KeepRelativeOffset, true); 20 21 if (!IsValid(DizzySoundAsset)) 22 { 23 UE_LOG(LogTemp, Error, TEXT("DizzySound is null, Function name: %s"), *FString(__FUNCTION__)); 24 } 25 else 26 { 27 DizzySound = UGameplayStatics::SpawnSoundAtLocation(GetWorld(), DizzySoundAsset, GetActorLocation()); 28 } 29 GetWorldTimerManager().SetTimer(DizzyTimerHandle, this, &APlayerCharacter::EndDizzy, DizzyDuration, false); 30}

    在即將生成特效之前,使用 LoadSynchronous() 同步載入特效素材是造成卡頓 的原因。

    LoadSynchronous()(同步載入)會等待載入完成(停止其他處理)。這使得玩家會感覺到卡頓。

    UE 官方建議使用非同步載入。

    參考影片: Maximizing Your Game's Performance in Unreal Engine | Unreal Fest 2022

    影片從 22:40 到 28:15。

    27:33 時展示了如何在 Blueprint 中實現非同步載入(AsyncLoad)。

    TSoftObjectPtr<UNiagaraSystem> DizzyEffectAsset
    特效素材在玩家BP裏持有。
    DizzyEffectAsset_wsa9ro

    調查結果: 原因是生成特效之前的 LoadSynchronous()

    實現非同步載入(AsyncLoad)

    為了解決由同步載入造成的卡頓,我們會在遊戲開始時提前以非同步方式載入素材(預讀)。

    非同步載入可能需要時間,所以如果確保載入的時間,生成素材的時候可能來不及。

    接下來,我們將在 C++ 中創建一個用於非同步載入的函數。

    PlayerCharacter.h
    1protected: 2 void OnDizzyEffectLoaded(); 3 void LoadDizzyEffectAsset();
    PlayerCharacter.cpp
    1void APlayerCharacter::BeginPlay() 2{ 3 Super::BeginPlay(); 4 LoadDizzyEffectAsset(); 5} 6 7void APlayerCharacter::LoadDizzyEffectAsset() 8{ 9 UE_LOG(LogTemp, Log, TEXT("DizzyEffectAsset requeset load")); 10 UAssetManager::Get().GetStreamableManager().RequestAsyncLoad(DizzyEffectAsset.ToSoftObjectPath(), 11 FStreamableDelegate::CreateUObject( 12 this, &APlayerCharacter::OnDizzyEffectLoaded)); 13} 14 15void APlayerCharacter::OnDizzyEffectLoaded() 16{ 17 UE_LOG(LogTemp, Log, TEXT("DizzyEffectLoaded")); 18 19 if (IsValid(DizzyEffectAsset.Get())) 20 { 21 UE_LOG(LogTemp, Log, TEXT("DizzyEffectAsset is valid")); 22 } 23 else 24 { 25 UE_LOG(LogTemp, Error, TEXT("DizzyEffectAsset is null, Function name: %s"), *FString(__FUNCTION__)); 26 } 27}

    接著,將素材的同步加載替換為 Get()

    PlayerCharacter.cpp
    1void APlayerCharacter::StartDizzy() 2{ 3 if (IsDizzy) 4 { 5 return; 6 } 7 CharacterMovementComponent->MaxWalkSpeed = DizzySpeed; 8 9 10 IsDizzy = true; 11 12 UNiagaraSystem* DizzyEffectSystem = DizzyEffectAsset.Get(); 13 if (!IsValid(DizzyEffectSystem)) 14 { 15 UE_LOG(LogTemp, Error, TEXT("DizzyEffectSystem is null, Function name: %s"), *FString(__FUNCTION__)); 16 } 17 DizzyEffect = UNiagaraFunctionLibrary::SpawnSystemAttached(DizzyEffectSystem, SceneComponent, NAME_None, 18 DizzyEffectOffset, FRotator::ZeroRotator, 19 EAttachLocation::KeepRelativeOffset, true); 20 21 if (!IsValid(DizzySoundAsset)) 22 { 23 UE_LOG(LogTemp, Error, TEXT("DizzySound is null, Function name: %s"), *FString(__FUNCTION__)); 24 } 25 else 26 { 27 DizzySound = UGameplayStatics::SpawnSoundAtLocation(GetWorld(), DizzySoundAsset, GetActorLocation()); 28 } 29 GetWorldTimerManager().SetTimer(DizzyTimerHandle, this, &APlayerCharacter::EndDizzy, DizzyDuration, false); 30}

    這樣非同步載入的實現就完成了。
    接下來,我們在 Standalone 遊戲中進行確認。

    如果Log顯示 DizzyEffectAsset is valid,則可以確認素材的非同步載入(AsyncLoad)已成功。

    結果

    使用「stat UnitGraph」命令的結果顯示,圖表上的尖峰消失,卡頓問題得以解決。

    總結

    調查流程

    • 卡頓的檢測:使用「stat unitgraph」命令將遊戲中發生的卡頓圖形化,並確定重負載發生的位置。
    • Unreal Insight 的測量:為了調查卡頓發生時的詳細負載,使用 trace.start 和 trace.stop 命令收集追蹤數據,確認負載的來源。

    問題的確定

    • 特效的素材(Niagara 特效)因同步載入 LoadSynchronous() 而被載入,因此在載入完成之前,其他處理會被停止,導致卡頓的發生。

    非同步載入的實現

    • 為了避免卡頓,使用 TSoftObjectPtr 在遊戲開始時將特效非同步載入。這樣,玩家在使用特效之前,素材已經載入完成,從而避免卡頓。

    結果

    非同步載入的實現成功解決了卡頓問題,遊戲的流暢度得到了確認。通過使用非同步載入,我們提升了遊戲性能,改善了玩家的體驗。

    1

    評論

    沒有評論

    發表閣下的感受